package com.liyong.pullorder.channels.core;

import com.liyong.pullorder.channels.autoconfigure.ChannelsPullProperties;
import com.liyong.pullorder.channels.auth.AuthStrategy;
import com.liyong.pullorder.channels.dto.PullResponse;
import com.liyong.pullorder.channels.dto.StandardOrderDto;
import com.liyong.pullorder.channels.parse.ParserStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
/**
 * 多渠道拉单核心服务。
 *
 * 设计要点：
 * - 通过策略接口 `AuthStrategy` 与 `ParserStrategy` 解耦认证与解析，便于扩展新渠道。
 * - `@Retryable` 提供指数退避重试，提升网络不稳定场景下的成功率。
 * - 统一输出 `PullResponse`，规范跨渠道返回结构。
 */
public class ChannelsPullService {
    private final ChannelsPullProperties properties;
    private final RestTemplate restTemplate;
    private final Map<String, AuthStrategy> authStrategies;
    private final Map<String, ParserStrategy> parserStrategies;

    public ChannelsPullService(ChannelsPullProperties properties,
                               RestTemplate restTemplate,
                               List<AuthStrategy> authStrategies,
                               List<ParserStrategy> parserStrategies) {
        this.properties = properties;
        this.restTemplate = restTemplate;
        this.authStrategies = authStrategies.stream().collect(Collectors.toMap(s -> s.getType().toLowerCase(), s -> s));
        this.parserStrategies = parserStrategies.stream().collect(Collectors.toMap(s -> s.getFormat().toLowerCase(), s -> s));
        log.info("ChannelsPullService 初始化完成 - 支持认证类型: {}, 支持解析格式: {}", this.authStrategies.keySet(), this.parserStrategies.keySet());
    }

    /**
     * 执行单个平台的拉单流程。
     *
     * 流程说明：
     * 1. 读取平台配置（基础 URL、认证、解析等），若未配置则直接失败。
     * 2. 构建请求头（含平台静态头 + 认证头）。
     * 3. 组合拉单 URL 并发起 HTTP GET 请求。
     * 4. 根据配置指定的解析器，将响应体转换为 `StandardOrderDto` 列表。
     * 5. 包装为统一响应结构 `PullResponse` 返回。
     *
     * 重试策略：
     * - 对 `ResourceAccessException` 做指数退避重试（最大3次，1s起步，倍数2）。
     */
    @Retryable(value = {org.springframework.web.client.ResourceAccessException.class},
            maxAttempts = 3,
            backoff = @Backoff(delay = 1000, multiplier = 2.0))
    public PullResponse pull(String platformCode) {
        ChannelsPullProperties.Platform pf = properties.getPlatforms().get(platformCode);
        if (pf == null) { return PullResponse.fail("平台未配置: " + platformCode); }
        try {
            // 1) 构建请求头（合并静态头与认证头）
            HttpHeaders headers = buildHeaders(pf);
            // 2) 计算请求 URL（补齐 /orders 路径）
            String url = buildUrl(pf);
            // 3) 发起请求
            ResponseEntity<String> resp = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(headers), String.class);
            // 4) 选择并执行解析器
            ParserStrategy parser = parserStrategies.get(pf.getParser().getFormat() == null ? "" : pf.getParser().getFormat().toLowerCase());
            if (parser == null) { return PullResponse.fail("解析器未找到: " + pf.getParser().getFormat()); }
            List<StandardOrderDto> orders = parser.parse(resp.getBody(), pf.getParser().getFieldMapping());
            // 5) 返回统一响应
            return PullResponse.ok(orders);
        } catch (Exception e) {
            log.error("拉单失败 - 平台: {}, 错误: {}", platformCode, e.getMessage());
            return PullResponse.fail("拉单失败: " + e.getMessage());
        }
    }

    /**
     * 返回当前已配置的平台编码列表（按字典序）。
     */
    public List<String> supportedPlatforms() {
        return properties.getPlatforms().keySet().stream().sorted().collect(Collectors.toList());
    }

    /**
     * 根据平台配置构建请求头。
     *
     * 合并策略：
     * - 静态头：直接透传。
     * - 认证头：由具体 `AuthStrategy` 构建并追加到请求头。
     */
    private HttpHeaders buildHeaders(ChannelsPullProperties.Platform pf) {
        HttpHeaders headers = new HttpHeaders();
        if (pf.getHeaders() != null) {
            pf.getHeaders().forEach(headers::add);
        }
        if (pf.getAuth() != null) {
            AuthStrategy auth = authStrategies.get(pf.getAuth().getType() == null ? "" : pf.getAuth().getType().toLowerCase());
            if (auth != null) { auth.apply(headers, pf.getAuth().getConfig()); }
        }
        return headers;
    }

    /**
     * 计算拉单请求 URL。
     *
     * 规则：
     * - 必须包含 `/orders` 资源路径；若平台配置为基础 URL，则自动补齐。
     * - 当基础 URL 以 `/` 结尾时，避免重复斜杠。
     */
    private String buildUrl(ChannelsPullProperties.Platform pf) {
        String base = pf.getBaseUrl();
        if (base == null || base.isEmpty()) { throw new IllegalArgumentException("平台baseUrl未配置"); }
        if (!base.endsWith("/orders")) {
            return base.endsWith("/") ? base + "orders" : base + "/orders";
        }
        return base;
    }
}